﻿#region MIT

//  /*The MIT License (MIT)
// 
//  Copyright 2016 lizs lizs4ever@163.com
//  
//  Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
//  
//  The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
//  
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//  THE SOFTWARE.
//   * */

#endregion

using System;
using System.Diagnostics;
#if NET35
#else
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Concurrent;

#endif

namespace mom
{
    /// <summary>
    ///     逻辑服务
    ///     1、可将多线程任务转换为单线程任务
    ///     2、提供定时调度、协程调度服务
    /// </summary>
    public sealed class Loop : SynchronizationContext
    {
        /// <summary>
        ///     instance
        /// </summary>
        public static Loop Ins { get; } = new Loop();

        private bool _quit;
        private readonly Stopwatch _watch = new Stopwatch();
        private readonly BlockingCollection<IJob> _queue = new BlockingCollection<IJob>();
        public int ThreadId { get; }


        /// <summary>
        ///     定时器调度器
        /// </summary>
        private TimerManager TimerMgr { get; } = new TimerManager();

        /// <summary>
        /// </summary>
        public Loop()
        {
            //SetSynchronizationContext(this);
            ThreadId = Thread.CurrentThread.ManagedThreadId;
        }

        /// <summary>
        ///     在本服务执行一个异步方法
        /// </summary>
        /// <param name="cb"></param>
        /// <param name="s"></param>
        public override void Post(SendOrPostCallback cb, object s)
        {
            Enqueue(cb, s);
        }

        ///// <summary>
        /////     在本服务同步执行
        ///// </summary>
        ///// <param name="cb"></param>
        ///// <param name="state"></param>
        //public override void Send(SendOrPostCallback cb, object state)
        //{
        //    using (new SyncContextHelper())
        //    {
        //        cb(state);
        //    }
        //}

        /// <summary>
        ///     投递到Loop执行
        /// </summary>
        /// <param name="cb"></param>
        public void Post(Action cb)
        {
            Post(state => cb(), null);
        }

        /// <summary>
        ///     投递到Loop执行
        /// </summary>
        /// <param name="cb"></param>
        /// <param name="arg"></param>
        /// <typeparam name="T"></typeparam>
        public void Post<T>(Action<T> cb, T arg)
        {
            Post(state => cb(arg), null);
        }

        /// <summary>
        ///     投递到Loop执行
        /// </summary>
        /// <param name="cb"></param>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public async Task<T> Post<T>(Func<Task<T>> cb)
        {
            var cs = new TaskCompletionSource<T>();
            Post(async () =>
            {
                var ret = await cb();
                cs.SetResult(ret);
            });

            return await cs.Task;
        }

        /// <summary>
        ///     投递到Loop执行
        /// </summary>
        /// <param name="cb"></param>
        /// <param name="arg"></param>
        /// <typeparam name="T1"></typeparam>
        /// <typeparam name="T2"></typeparam>
        /// <returns></returns>
        public async Task<T2> Post<T1, T2>(Func<T1, Task<T2>> cb, T1 arg)
        {
            var cs = new TaskCompletionSource<T2>();
            Post(async () =>
            {
                var ret = await cb(arg);
                cs.SetResult(ret);
            });

            return await cs.Task;
        }

        public async Task<T3> Post<T1, T2, T3>(Func<T1, T2, Task<T3>> cb, T1 arg1, T2 arg2)
        {
            var cs = new TaskCompletionSource<T3>();
            Post(async () =>
            {
                var ret = await cb(arg1, arg2);
                cs.SetResult(ret);
            });

            return await cs.Task;
        }

#if NET35
#else
        ///// <summary>
        /////     在非Loop线程执行，并在loop线程返回执行结果
        ///// </summary>
        ///// <typeparam name="TRet"></typeparam>
        ///// <param name="fun"></param>
        ///// <returns></returns>
        //public async Task<TRet> PostWorkItem<TRet>(Func<TRet> fun)
        //{
        //    var ret = default(TRet);
        //    var task = Task.Run(() => { ret = fun(); });

        //    //using (new DebugHelper("PostWorkItem"))
        //    using (new SyncContextHelper())
        //    {
        //        await task.ContinueWith(t => { }, TaskScheduler.FromCurrentSynchronizationContext());
        //        return ret;
        //    }
        //}

        ///// <summary>
        /////     excute fun in non-loop thread, but the excute result returned  in loop thread
        ///// </summary>
        ///// <typeparam name="TRet"></typeparam>
        ///// <typeparam name="T"></typeparam>
        ///// <param name="fun"></param>
        ///// <param name="param"></param>
        ///// <returns></returns>
        //public async Task<TRet> PostWorkItem<TRet, T>(Func<T, TRet> fun, T param)
        //{
        //    var ret = default(TRet);
        //    var task = Task.Run(() => { ret = fun(param); });

        //    //using (new DebugHelper("PostWorkItem"))
        //    using (new SyncContextHelper())
        //    {
        //        await task.ContinueWith(t => { }, TaskScheduler.FromCurrentSynchronizationContext());
        //        return ret;
        //    }
        //}

        ///// <summary>
        ///// </summary>
        //public async Task<TRet> PostWorkItem<TRet, T1, T2>(Func<T1, T2, TRet> fun, T1 param1, T2 param2)
        //{
        //    var ret = default(TRet);
        //    var task = Task.Run(() => { ret = fun(param1, param2); });

        //    //using (new DebugHelper("PostWorkItem"))
        //    using (new SyncContextHelper())
        //    {
        //        await task.ContinueWith(t => { }, TaskScheduler.FromCurrentSynchronizationContext());
        //        return ret;
        //    }
        //}

        ///// <summary>
        /////     excute fun in non-loop thread, but the excute result returned  in loop thread
        ///// </summary>
        //public async Task<TRet> PostWorkItem<TRet, T1, T2, T3>(Func<T1, T2, T3, TRet> fun, T1 param1, T2 param2,
        //    T3 param3)
        //{
        //    var ret = default(TRet);
        //    var task = Task.Run(() => { ret = fun(param1, param2, param3); });

        //    using (new DebugHelper("PostWorkItem"))
        //    using (new SyncContextHelper())
        //    {
        //        await task.ContinueWith(t => { }, TaskScheduler.FromCurrentSynchronizationContext());
        //        return ret;
        //    }
        //}
#endif

        /// <summary>
        ///     添加定时器
        /// </summary>
        /// <param name="timer"></param>
        public void QueueTimer(Timer timer)
        {
            TimerMgr.Add(timer);
        }

        /// <summary>
        ///     移除定时器
        /// </summary>
        /// <param name="timer"></param>
        public void DequeueTimer(Timer timer)
        {
            TimerMgr.Remove(timer);
        }

        /// <summary>
        ///     Specify the working period of the working thread in milliseconds.
        ///     That is, every period,the working thread loop back to the working
        ///     procedure's top. and, the Idle event is called.
        /// </summary>
        public int Period { get; set; } = 10;

        /// <summary>
        ///     Specify the work items count currently in working queue.
        /// </summary>
        public int Jobs => _queue.Count;

        /// <summary>
        ///     Get the elapsed milliseconds since the instance been constructed
        /// </summary>
        public long ElapsedMilliseconds => _watch.ElapsedMilliseconds;

        /// <summary>
        ///     External(e.g. the TCP socket thread) call this method to push
        ///     a work item into the working queue. The work item must not
        ///     be null.
        /// </summary>
        /// <param name="w">the work item object, must not be null</param>
        private void Enqueue(IJob w)
        {
            _queue.TryAdd(w);
        }

        /// <summary>
        ///     External(e.g. the TCP socket thread) call this method to push
        ///     a work item into the working queue.
        /// </summary>
        /// <param name="proc">the working procedure</param>
        /// <param name="param">additional parameter that passed to working procedure</param>
        private void Enqueue<T>(Action<T> proc, T param)
        {
            var w = new Job<T>(proc, param);
            Enqueue(w);
        }


        private void Enqueue(SendOrPostCallback cb, object state)
        {
            var w = new JobAsync(cb, state);
            Enqueue(w);
        }

        /// <summary>
        ///     入队
        /// </summary>
        /// <param name="proc"></param>
        private void Enqueue(Action proc)
        {
            var job = new Job(proc);
            Enqueue(job);
        }

        /// <summary>
        ///     主循环
        /// </summary>
        public void Run()
        {
            _watch.Start();

            while (!_quit)
            {
                // 1、 定时器调度
                TimerMgr.Update(ElapsedMilliseconds);

                // 2、 协程调度
                CoroutineScheduler.Ins.Update();

                // 3、 job调度
                var enterTime = ElapsedMilliseconds;
                var outer = Current;
                SetSynchronizationContext(this);

                try
                {
                    do
                    {
                        if (_queue.TryTake(out var item, Period))
                        {
                            item.Do();
                        }
                        else
                            break;
                    } while (ElapsedMilliseconds - enterTime < Period);
                }
                catch (Exception ex)
                {
                    Logger.Ins.Fatal("{0} : {1}", ex.Message, ex.StackTrace);
                }
                finally
                {
                    SetSynchronizationContext(outer);
                }
            }

            _watch.Stop();
        }

        /// <summary>
        ///     stop the loop
        /// </summary>
        public void Stop()
        {
            _quit = true;
        }
    }
}